/*

Copyright (c) 2021 唐佐林
WeChat : delphi_tang
EMail: delphi_tang@dt4sw.com

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

*/

#include "IPCEvent.h"
#include "ReplApp.h"
#include "CmdLine.h"
#include "ReadFileThread.h"
#include "SerialMsgParser.h"
#include <QCoreApplication>
#include <QDir>
#include <QFile>

static volatile bool g_ref = false;
static QDateTime g_timestamp;

static QMap<QString, QDateTime> adjustIpMap(const QMap<QString, QDateTime>& map, const QDateTime& ts)
{
    QMap<QString, QDateTime> ret;
    QList<QString> ips = map.keys();

    for(int i=0; i<ips.size(); i++)
    {
        if( map[ips[i]].msecsTo(ts) < 3000 )
        {
            ret.insert(ips[i], map[ips[i]]);
        }
    }

    return ret;
}

static void ToObtain()
{
    if( !g_ref )
    {
        CmdLine::Instance().obtain();

        g_ref = true;
    }
}

static void ToGiveup()
{
    if( g_ref )
    {
        CmdLine::Instance().giveup();

        g_ref = false;
    }
}

ReplApp& ReplApp::Instance()
{
    static ReplApp* pApp = NULL;

    if( !pApp )
    {
        pApp = new ReplApp();
    }

    return *pApp;
}

static QString GetWorkspace()
{
    QString ret = QCoreApplication::applicationDirPath();
    QFile file(ret + "\\ws.txt");

    if( file.open(QIODevice::ReadOnly | QIODevice::Text) )
    {
        ret = QString(file.readAll());
    }

    if( !QFile(ret).exists() )
    {
        ret = QCoreApplication::applicationDirPath();
    }

    return ret;
}

static bool SetWorkspace(QString path)
{
    QFile file(QCoreApplication::applicationDirPath() + "\\ws.txt");
    bool ret = file.open(QIODevice::WriteOnly | QIODevice::Text);

    if( ret )
    {
        file.write(path.toUtf8());
    }

    return ret;
}

ReplApp::ReplApp()
{
    connect(&m_timer, SIGNAL(timeout()), this, SLOT(onTimeout()));
    connect(&m_sp, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
    connect(&CmdLine::Instance(), SIGNAL(command(QString)), this, SLOT(onCommand(QString)), Qt::BlockingQueuedConnection);

    m_msgHandlerMap.insert(IPC_TYPE_STATE, &ReplApp::State_Handler);
    m_msgHandlerMap.insert(IPC_TYPE_CMD, &ReplApp::Cmd_Handler);
    m_msgHandlerMap.insert(IPC_TYPE_MSG, &ReplApp::Msg_Handler);

    m_cmdHandlerMap.insert("query", CmdParam(&ReplApp::Query_Handler, false));
    m_cmdHandlerMap.insert("config", CmdParam(&ReplApp::Config_Handler, false));
    m_cmdHandlerMap.insert("connect", CmdParam(&ReplApp::Connect_Handler, false));
    m_cmdHandlerMap.insert("disconnect", CmdParam(&ReplApp::Disconnect_Handler, true));
    m_cmdHandlerMap.insert("install", CmdParam(&ReplApp::Install_Handler, true));
    m_cmdHandlerMap.insert("run", CmdParam(&ReplApp::Run_Handler, true));
    m_cmdHandlerMap.insert("workspace", CmdParam(&ReplApp::Workspace_Handler, false));
    m_cmdHandlerMap.insert("exit", CmdParam(&ReplApp::Exit_Handler, false));

    m_client.setHandler(this);

    m_endpoint.setHandler(this);
    m_endpoint.bind(RXPORT);
}

void ReplApp::onCommand(QString cmd)
{
    process(cmd);
}

void ReplApp::onReadFileProcess(ushort index, ushort total, const QByteArray& data)
{
    if( index == 0 )
    {
        QString text = "[%1/%2] process file: %3";
        QString fn = QFileInfo(QString(data)).fileName();

        text = text.arg(index).arg(total).arg(fn);

        appendText(text);
        appendLine();

        m_client.send(IPCMessage(IPC_TYPE_CMD, IPC_CMD_FILE, index, total, fn.toStdString().c_str(), fn.length() + 1));
    }
    else
    {
        QString text = "[%1/%2] process %3 bytes";

        text = text.arg(index).arg(total).arg(data.length());

        appendText(text);
        appendLine();

        m_client.send(IPCMessage(IPC_TYPE_CMD, IPC_CMD_FILE, index, total, data));
    }
}

void ReplApp::onReadyRead()
{
    static SerialMsgParser s_parser("<PY4OH>", "<PY4OH>\v");

    s_parser.put(m_sp.readAll());

    if( s_parser.parse() )
    {
        appendText(s_parser.result());
        appendLine();

        m_sp.close();

        s_parser.reset();

        ToGiveup();
    }
}

void ReplApp::Config_Wifi_Handler(QString port, QString id, QString pwd)
{
    serialPortAction(port,
                     QString("AT+CONFIG=wifi,%1,%2\r\n").arg(id).arg(pwd),
                     QString("Can NOT query device from \'%1\'.").arg(port));
}

void ReplApp::Config_Mode_Handler(QString port, QString mode)
{
    serialPortAction(port,
                     QString("AT+CONFIG=mode,%1\r\n").arg(mode),
                     QString("Can NOT query device from \'%1\'.").arg(port));
}

void ReplApp::Config_Handler(QString arg)
{
    QStringList argList = arg.split(",");
    QString port = (argList.size() > 1) ? argList[0].trimmed() : "";
    QString attr = (argList.size() > 2) ? argList[1].trimmed() : "";
    QString err = "";

    if( argList.size() < 3 )
    {
        err = "The arguments for \'config\' are NOT valid.";
    }
    else if( attr == "wifi" )
    {
        QString id = (argList.size() == 4) ? argList[2].trimmed() : "";
        QString pwd = (argList.size() == 4) ? argList[3].trimmed() : "";

        if( (id != "") && (pwd != "") )
        {
            Config_Wifi_Handler(port, id, pwd);
        }
        else
        {
            err = "The parameters for \'%1\' are NOT valid.";
            err = err.arg(attr);
        }
    }
    else if( attr == "mode" )
    {
        QString mode = (argList.size() == 3) ? argList[2].trimmed() : "";

        if( mode != "" )
        {
            Config_Mode_Handler(port, mode);
        }
        else
        {
            err = "The parameter for \'%1\' is NOT valid.";
            err = err.arg(attr);
        }
    }
    else
    {
        err = "\'%1\' is NOT supported!";
        err = err.arg(attr);
    }

    if( err != "" )
    {
        appendText(err);
        appendLine(2);
    }
}

bool ReplApp::writeToDev(QString port, QString atcmd)
{
    bool ret = false;

    if( !m_sp.isOpen() )
    {
        m_sp.setPortName(port);
        m_sp.setBaudRate(QSerialPort::Baud115200);
        m_sp.setDataBits(QSerialPort::Data8);
        m_sp.setParity(QSerialPort::NoParity);
        m_sp.setStopBits(QSerialPort::OneStop);
        m_sp.setFlowControl(QSerialPort::NoFlowControl);

        if( m_sp.open(QIODevice::ReadWrite) )
        {
            ret = (m_sp.write(atcmd.toLatin1()) != -1);
        }
    }

    return ret;
}

void ReplApp::serialPortAction(QString port, QString atcmd, QString err)
{
    if( writeToDev(port, atcmd) )
    {
        ToObtain();
    }
    else
    {
        appendText(err);
        appendLine(2);
    }
}

void ReplApp::Query_Handler(QString arg)
{
    if( arg != "" )
    {
        serialPortAction(arg,
                         QString("AT+QUERY\r\n"),
                         QString("Can NOT query device from \'%1\'.").arg(arg));
    }
    else
    {
        appendText("The argument for \'query\' can NOT be empty.");
        appendLine(2);
    }
}

void ReplApp::Install_Handler(QString arg)
{
    QFile file(GetWorkspace() + "\\" + arg);

    if( arg != "" )
    {
        if( file.exists() )
        {
            ReadFileThread* t = ReadFileThread::Create(file.fileName());

            connect(t, SIGNAL(process(ushort,ushort,QByteArray)), this, SLOT(onReadFileProcess(ushort,ushort,QByteArray)));

            ToObtain();

            t->start();
        }
        else
        {
            QString err = "\'%1\' is NOT existed!";

            appendText(err.arg(arg));
            appendLine(2);
        }
    }
    else
    {
        appendText("Parameter can NOT be empty.");
        appendLine(2);
    }
}

void ReplApp::Run_Handler(QString arg)
{    
    if( arg != "" )
    {
        ToObtain();

        m_client.send(IPCMessage(IPC_TYPE_CMD, IPC_CMD_RUN, 0, 0, arg.toStdString().c_str(), arg.length() + 1));
    }
    else
    {
        appendText("Parameter can NOT be empty.");
        appendLine(2);
    }
}

void ReplApp::Connect_Handler(QString arg)
{
    if( !m_client.isValid() )
    {
        m_ipMap = adjustIpMap(m_ipMap, QDateTime::currentDateTime());

        if( arg == "" )
        {
            if( m_ipMap.size() == 1 )
            {
                arg = m_ipMap.keys()[0];
            }
            else if( m_ipMap.size() > 1 )
            {
                QList<QString> ips = m_ipMap.keys();

                appendText("Select a device to connect:");
                appendLine();

                for(int i=0; i<ips.size(); i++)
                {
                    appendText(ips[i]);
                    appendLine();
                }

                appendLine();

                return;
            }
        }

        if( arg != "" )
        {
            if( m_client.connectTo(arg.toStdString().c_str(), DEVPORT) )
            {
                appendText("Connection is OK!");
            }
            else
            {
                appendText("Can NOT connect to device!");

                m_ipMap.remove(arg);
            }
        }
        else
        {
            appendText("No device to connect!");
        }
    }
    else
    {
        appendText("Connection is established!");
    }

    appendLine(2);
}

void ReplApp::Disconnect_Handler(QString arg)
{
    if( arg == "" )
    {
        m_client.close();

        appendText("Disconnect from device.");
    }
    else
    {
        appendText("Invalid Parameter: " + arg);
    }

    appendLine(2);
}

void ReplApp::Workspace_Handler(QString arg)
{
    QString text = "";

    if( !arg.isEmpty() )
    {
        if( QDir(arg).exists() && SetWorkspace(arg) )
        {
            text = "Set \'%1\' as workspace!";
        }
        else
        {
            text = "\'%1\' is NOT existed!";
        }

        appendText(text.arg(arg));
    }
    else
    {
        appendText(GetWorkspace());
    }

    appendLine(2);
}

void ReplApp::Exit_Handler(QString)
{
    m_timer.stop();
    m_client.close();
    m_endpoint.close();

    CmdLine::Instance().stop();

    QCoreApplication::exit();
}

void ReplApp::handle(QAbstractSocket& sock, IPCMessage& msg)
{
    if( m_msgHandlerMap.contains(msg.type()) )
    {
        MsgHandler handler = m_msgHandlerMap.value(msg.type());

        (this->*handler)(sock, msg);
    }
}

void ReplApp::appendText(QString msg)
{
    CmdLine::Instance().print(msg);
}

void ReplApp::appendLine(int n)
{
    CmdLine::Instance().println(n);
}

void ReplApp::State_Handler(QAbstractSocket& sock, IPCMessage& msg)
{
    switch(msg.cmd())
    {
        case IPC_STATE_CONN:
            STATE_CONN_Handler(sock, msg);
            break;
        case IPC_STATE_DSCN:
            STATE_DSCN_Handler(sock, msg);
            break;
        case IPC_STATE_HOST:
            STATE_HOST_Handler(sock, msg);
            break;
        case IPC_STATE_SIGNAL:
            STATE_SIGNAL_Handler(sock, msg);
            break;
    }
}

void ReplApp::STATE_CONN_Handler(QAbstractSocket&, IPCMessage&)
{
    g_timestamp = QDateTime::currentDateTime();

    m_timer.start(500);
}

void ReplApp::STATE_DSCN_Handler(QAbstractSocket&, IPCMessage&)
{
    m_ipMap.clear();
    m_timer.stop();
    m_client.close();

    if( g_ref )
    {
        appendLine();
    }

    ToGiveup();
}

void ReplApp::STATE_SIGNAL_Handler(QAbstractSocket&, IPCMessage&)
{
    g_timestamp = QDateTime::currentDateTime();
}

void ReplApp::onTimeout()
{
    qint64 s = g_timestamp.msecsTo(QDateTime::currentDateTime());

    if( s > 5000 )
    {
        STATE_DSCN_Handler(*((QTcpSocket*)NULL), *((IPCMessage*)NULL));
    }
}

void ReplApp::STATE_HOST_Handler(QAbstractSocket&, IPCMessage& msg)
{
    m_ipMap.insert(QString(msg.payload()), QDateTime::currentDateTime());
}

void ReplApp::Cmd_Handler(QAbstractSocket& sock, IPCMessage& msg)
{
    int cmd = msg.cmd();

    switch(cmd)
    {
        case IPC_CMD_CODE_ACK:
            CMD_CODE_ACK_Handler(sock, msg);
            break;
        case IPC_CMD_RUN_ACK:
            CMD_RUN_ACK_Handler(sock, msg);
            break;
        case IPC_CMD_FILE_ACK:
            CMD_FILE_ACK_Handler(sock, msg);
            break;
    }
}

void ReplApp::Msg_Handler(QAbstractSocket& sock, IPCMessage& msg)
{
    if( msg.cmd() == IPC_MSG_OUTPUT )
    {
        STATE_SIGNAL_Handler(sock, msg);

        appendText(QByteArray(msg.payload(), msg.length()));
    }
}

void ReplApp::CMD_CODE_ACK_Handler(QAbstractSocket&, IPCMessage&)
{
    appendLine();

    ToGiveup();
}

void ReplApp::CMD_RUN_ACK_Handler(QAbstractSocket&, IPCMessage&)
{
    appendLine();

    ToGiveup();
}

void ReplApp::CMD_FILE_ACK_Handler(QAbstractSocket&, IPCMessage& msg)
{
    if( msg.index() == 0 )
    {
        appendText("File install is completed!");
        appendLine(2);
    }
    else
    {
        QString err = "File install error: %1";

        appendText(err.arg(msg.index()));
        appendLine(2);
    }

    ToGiveup();
}

void ReplApp::doCode(QString code)
{
    if( m_client.isValid () )
    {
        ToObtain();

        m_client.send(IPCMessage(IPC_TYPE_CMD, IPC_CMD_CODE, 0, 0, code.toStdString().c_str(), code.length() + 1));
    }
    else
    {
        appendText("Connection is NOT established!");
        appendLine(2);
    }
}

void ReplApp::doCmd(QString input)
{
    bool done = false;
    int left = input.indexOf('(');
    int right = input.lastIndexOf(')');

    if( (left > 0) && (right == (input.length() - 1)) )
    {
        QString cmd = input.left(left).trimmed();
        QString arg = input.mid(left + 1, right - left - 1).trimmed();

        if( m_cmdHandlerMap.contains(cmd) )
        {
            CmdParam cp = m_cmdHandlerMap.value(cmd);

            if( cp.conn ? m_client.isValid() : true )
            {
                (this->*cp.handler)(arg);
            }
            else
            {
                appendText("Connection is NOT established!");
                appendLine(2);
            }

            done = true;
        }
    }

    if( !done )
    {
        appendText("Invalid Command: ");
        appendText(input);
        appendLine(2);
    }
}

void ReplApp::process(QString text)
{
    if( !text.isEmpty() )
    {
        if( text[0] == '%' )
        {
            doCmd(text.right(text.length() - 1));
        }
        else
        {
            doCode(text);
        }
    }
}

void ReplApp::run()
{
    CmdLine::Instance().start();
}

ReplApp::~ReplApp()
{

}
